import asyncio
import math
import time
import requests
import os
import json
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import pandas as pd
import re
import glob
import threading
import logging
import queue
import sys
from datetime import datetime
from DrissionPage import ChromiumPage

# 创建日志队列和日志处理器
log_queue = queue.Queue()

class QueueHandler(logging.Handler):
    """将日志记录发送到队列"""
    def __init__(self, log_queue):
        super().__init__()
        self.log_queue = log_queue

    def emit(self, record):
        self.log_queue.put(record)

# 配置日志记录器
def setup_logger():
    """设置日志记录器"""
    logger = logging.getLogger('tme_handler')
    logger.setLevel(logging.INFO)
    
    # 添加队列处理器
    queue_handler = QueueHandler(log_queue)
    queue_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%H:%M:%S'))
    logger.addHandler(queue_handler)
    
    # 添加控制台处理器（可选，用于调试）
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%H:%M:%S'))
    logger.addHandler(console_handler)
    
    return logger

# 创建全局日志记录器
logger = setup_logger()

# 创建数据保存目录
def ensure_data_dir():
    """确保数据目录存在"""
    data_dir = "captured_data"
    if not os.path.exists(data_dir):
        os.makedirs(data_dir)
        logger.info(f"创建数据目录: {data_dir}")
    return data_dir

# 初始化数据目录
DATA_DIR = ensure_data_dir()

# 定义全局变量
role_cv_map = {}
cv_role_map = {}
is_login = False
chapter_titles = []
chapter_detail_map = {}
current_page_num = 1
total_page_num = math.ceil(2607/20)
start_crawling_event = threading.Event()  # 控制是否开始爬取数据的事件

###########################################
# 数据初始化部分 - 来自main.py
###########################################

async def start_listening_async(tab, urls):
    """开始异步监听多个URL请求"""
    # 使用通用模式匹配所有需要监听的URL
    # DrissionPage的listen.start()会覆盖之前的监听设置，所以我们需要使用一个通用模式
    
    # 方法1：使用基础域名作为通用模式
    tab.listen.start('https://gateway.kugou.com/')
    
    # 方法2：如果DrissionPage支持正则表达式，可以尝试使用正则模式（取决于库的实现）
    # tab.listen.start(r'https://gateway\.kugou\.com/longebook/.*')
    
    logger.info("已设置监听模式: https://gateway.kugou.com/")
    logger.info(f"将监听以下URL:")
    for i, url in enumerate(urls, 1):
        logger.info(f"  {i}. {url}")
    
    return len(urls)

async def process_packets_async(tab):
    """异步遍历监听到的数据包并处理"""
    while True:
        for packet in tab.listen.steps():
            try:
                # 获取URL和响应内容
                url = packet.request.url
                response_body = packet.response.body

                # 根据不同的URL进行不同的处理并保存到文件
                if 'longebook/v1/ai_design_album/roles' in url:
                    # 处理角色列表数据
                    save_with_deduplication(response_body, f"roles.json")
                    process_roles_data(response_body)
                elif 'longebook/v2/ai_design_anchor_task_center/chapter_list' in url:
                    # 处理章节列表数据
                    save_with_deduplication(response_body, f"chapter_list.json")
                    process_chapter_data(response_body)
                elif 'longebook/v1/ai_design_anchor_task_center/chapter_detail' in url:
                    # 处理章节详情数据

                    # 尝试从URL中提取章节ID
                    chapter_id = extract_chapter_id(url)

                    if chapter_id:
                        # 为每个章节ID保存一个单独的文件
                        filename = f"chapter_detail_{chapter_id}.json"

                        # 检查文件是否已存在
                        filepath = os.path.join(DATA_DIR, filename)
                        if os.path.exists(filepath):
                            save_with_deduplication(response_body, filename)
                        else:
                            save_to_file(response_body, filename)
                    else:
                        # 如果无法提取章节ID，使用时间戳
                        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                        save_to_file(response_body, f"chapter_detail_unknown_{timestamp}.json")

                    # 处理章节详情数据
                    process_chapter_detail_data(response_body)
                else:
                    # 忽略其他不相关的URL
                    pass
            except Exception as e:
                logger.info(f"处理数据包时出错: {str(e)}")

        # 使用更合理的休眠时间，减少CPU使用率但保持响应速度
        await asyncio.sleep(0.1)

def extract_chapter_id(url):
    """从URL中提取章节ID"""
    try:
        # 方法1：使用urllib.parse库解析URL参数
        from urllib.parse import urlparse, parse_qs

        # 解析URL
        parsed_url = urlparse(url)

        # 解析查询参数
        query_params = parse_qs(parsed_url.query)

        # 获取chapter_id参数
        if 'chapter_id' in query_params:
            return query_params['chapter_id'][0]

        # 方法2：如果URL中包含chapter_id但不是作为查询参数
        import re

        # 尝试匹配路径中的chapter_id
        path_match = re.search(r'/chapter_id=(\d+)', parsed_url.path)
        if path_match:
            return path_match.group(1)

        # 方法3：回退到简单的字符串分割方法
        if 'chapter_id=' in url:
            parts = url.split('chapter_id=')
            if len(parts) > 1:
                chapter_id = parts[1].split('&')[0]
                return chapter_id

        # 尝试从URL中提取任何看起来像ID的数字
        id_match = re.search(r'[?&/]id=(\d+)', url)
        if id_match:
            return id_match.group(1)

        logger.info(f"无法从URL中提取chapter_id: {url}")
    except Exception as e:
        logger.info(f"提取chapter_id时出错: {str(e)}")

    return None

def save_to_file(data, filename, append_mode=False):
    """将数据保存到文件"""
    try:
        filepath = os.path.join(DATA_DIR, filename)

        # 如果数据是字典，转换为JSON字符串
        if isinstance(data, dict):
            content = json.dumps(data, ensure_ascii=False, indent=2)
        else:
            # 如果已经是字符串或其他类型，尝试直接保存
            content = str(data)

        # 根据模式选择写入方式
        mode = 'a' if append_mode else 'w'
        with open(filepath, mode, encoding='utf-8') as f:
            f.write(content)
    except Exception as e:
        logger.info(f"保存文件时出错: {str(e)}")

def save_with_deduplication(data, filename):
    """读取现有文件，合并新数据，去重后保存"""
    try:
        filepath = os.path.join(DATA_DIR, filename)

        # 将输入数据转换为字典（如果是字符串）
        if isinstance(data, str):
            try:
                data = json.loads(data)
            except json.JSONDecodeError:
                logger.info("输入数据不是有效的JSON字符串")
                # 如果不是有效的JSON，直接保存
                save_to_file(data, filename)
                return

        # 如果文件不存在，直接保存
        if not os.path.exists(filepath):
            save_to_file(data, filename)
            return

        # 读取现有文件
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                existing_data = json.load(f)
        except (json.JSONDecodeError, FileNotFoundError):
            # 如果文件不存在或不是有效的JSON，直接保存新数据
            save_to_file(data, filename)
            return

        # 合并数据
        if 'data' in data and 'data' in existing_data:
            # 如果两者都有data字段，合并列表
            if 'list' in data['data'] and 'list' in existing_data['data']:
                # 创建一个ID集合，用于去重
                existing_ids = set()
                merged_list = []

                # 添加现有数据，并记录ID
                for item in existing_data['data']['list']:
                    item_id = item.get('id', None)
                    if item_id is not None:
                        existing_ids.add(item_id)
                    merged_list.append(item)

                # 添加新数据，但跳过已存在的ID
                for item in data['data']['list']:
                    item_id = item.get('id', None)
                    if item_id is None or item_id not in existing_ids:
                        merged_list.append(item)
                        if item_id is not None:
                            existing_ids.add(item_id)

                # 更新合并后的列表
                existing_data['data']['list'] = merged_list
            else:
                # 如果结构不同，使用新数据
                existing_data = data
        else:
            # 如果结构不同，使用新数据
            existing_data = data

        # 保存合并后的数据
        save_to_file(existing_data, filename)

    except Exception as e:
        logger.info(f"合并数据时出错: {str(e)}")
        # 出错时，尝试直接保存新数据
        save_to_file(data, filename)

def process_roles_data(data):
    """处理角色列表数据"""
    try:
        if not data:
            logger.info("角色列表数据为空")
            return

        # 尝试将字符串转换为JSON（如果它是字符串的话）
        if isinstance(data, str):
            try:
                data = json.loads(data)
            except json.JSONDecodeError:
                logger.info("角色列表数据不是有效的JSON字符串")
                return

        # 检查数据结构
        if not isinstance(data, dict):
            logger.info(f"角色列表数据不是字典格式: {type(data)}")
            return

        if 'data' not in data:
            logger.info("角色列表数据缺少'data'字段")
            return

        if 'list' not in data['data']:
            logger.info("角色列表数据缺少'list'字段")
            return

        # 处理数据 - 只保留少量数据在内存中，其余已保存到文件
        role_count = 0
        cv_count = 0

        for item in data['data']['list']:
            if 'name' not in item or 'cv_name' not in item:
                continue

            role_name = item['name']
            cv_name = item['cv_name']

            # 只保存前100个角色到内存中，避免内存溢出
            if len(role_cv_map) < 100:
                role_cv_map[role_name] = cv_name

                if cv_name not in cv_role_map:
                    cv_role_map[cv_name] = [role_name]
                else:
                    cv_role_map[cv_name].append(role_name)

            role_count += 1
            if cv_name not in cv_role_map:
                cv_count += 1

        logger.info(f"读取主播列表成功，共获取 {role_count} 个角色，{cv_count} 个配音")

        # 打印部分数据作为示例
        if role_cv_map:
            sample_items = list(role_cv_map.items())[:3]
            logger.info("示例数据:")
            for role, cv in sample_items:
                logger.info(f"  角色: {role}, 配音: {cv}")
    except Exception as e:
        logger.info(f"处理角色列表数据时出错: {str(e)}")

def process_chapter_detail_data(data):
    """处理章节详情数据"""
    try:
        logger.info("开始处理章节详情数据")

        if not data:
            logger.info("章节详情数据为空")
            return

        # 尝试将字符串转换为JSON（如果它是字符串的话）
        if isinstance(data, str):
            try:
                data = json.loads(data)
                logger.info(f"成功将字符串解析为JSON数据，类型: {type(data).__name__}")
            except json.JSONDecodeError:
                logger.info("章节详情数据不是有效的JSON字符串")
                # 保存原始数据以便调试
                debug_filename = f"debug_invalid_json_{int(time.time())}.txt"
                save_to_file(data[:1000] + "...(截断)" if len(data) > 1000 else data, debug_filename)
                logger.info(f"已保存无效JSON数据片段到: {debug_filename}")
                return

        # 检查数据结构
        if not isinstance(data, dict):
            logger.info(f"章节详情数据不是字典格式: {type(data)}")
            return

        if 'data' not in data:
            logger.info("章节详情数据缺少'data'字段")
            logger.info(f"可用字段: {', '.join(data.keys())}")
            return

        # 安全地访问嵌套字段
        if 'data' in data and isinstance(data['data'], dict):
            chapter_data = data['data']
            chapter_id = chapter_data.get('id', 'unknown_id')
            chapter_title = chapter_data.get('title', 'unknown_title')
            
            logger.info(f"成功处理章节详情: {chapter_title} (ID: {chapter_id})")
            
            # 检查是否有错误行信息
            if 'error_lines' in chapter_data and chapter_data['error_lines']:
                error_count = len(chapter_data['error_lines'])
                logger.info(f"发现 {error_count} 个错误行需要处理")
    except Exception as e:
        logger.info(f"处理章节详情数据时出错: {str(e)}")
        import traceback
        traceback.print_exc()

def process_chapter_data(data):
    """处理章节列表数据"""
    global chapter_titles
    
    try:
        if not data:
            logger.info("章节列表数据为空")
            return
            
        # 尝试将字符串转换为JSON（如果它是字符串的话）
        if isinstance(data, str):
            try:
                data = json.loads(data)
            except json.JSONDecodeError:
                logger.info("章节列表数据不是有效的JSON字符串")
                return
        
        # 检查数据结构
        if not isinstance(data, dict):
            logger.info(f"章节列表数据不是字典格式: {type(data)}")
            return
            
        if 'data' not in data:
            logger.info("章节列表数据缺少'data'字段")
            return
            
        if 'list' not in data['data']:
            logger.info("章节列表为空或缺少'list'字段")
            return
            
        # 处理数据 - 只保留章节标题，不存储完整数据
        chapters = data['data']['list']
        
        # 清空之前的章节标题列表，避免重复添加
        chapter_titles = []
        
        # 只存储章节标题
        for i, chapter in enumerate(chapters):
            title = chapter.get('title', f"未命名章节{i+1}")
            chapter_titles.append(title)
        
        logger.info(f"读取章节列表成功，共获取 {len(chapter_titles)} 个章节")
    except Exception as e:
        logger.info(f"处理章节列表数据时出错: {str(e)}")

def login(cur_page):
    """等待用户登录"""
    global is_login
    start_time = time.time()
    timeout = 300
    while time.time() - start_time < timeout:
        try:
            if "创建专辑" not in cur_page.html and "上传作品" not in cur_page.html:
                logger.info("使用微信扫一扫登录")
                time.sleep(3)
            else:
                is_login = True
                logger.info("恭喜你，登陆成功！")
                break
        except:
            time.sleep(1)
    else:
        logger.info("登录超时，请重试")

def getChapterTitle(cur_page):
    """获取章节标题"""
    global chapter_titles

    cur_page.get(
        'https://mp.tencentmusic.com/aigc/ndx?aigc=%2Fhuaben%2Fv-514ab38f%2Findex.html%3Fcors%3D1%23%2Fanchor%2Fupload#/anchor/upload?cid=211029&page=1'
    )
    cur_page.wait.load_start()
    cur_page.wait.doc_loaded()
    time.sleep(2)
    try:
        timeout = 100
        start_time = time.time()

        is_clicked = False
        while time.time() - start_time < timeout:
            if is_clicked:
                break
            # 获取所有iframe元素（<iframe>标签）
            iframe_eles = cur_page.eles('t:iframe')  # 通过标签名获取所有iframe元素
            if iframe_eles:
                for iframe_ele in iframe_eles:
                    # 从iframe元素中获取name属性
                    frame_name = iframe_ele.attr("name")
                    if frame_name == "aigc":
                        # 通过名称获取对应的框架对象
                        target_frame = cur_page.get_frame(frame_name)
                        # 使用JavaScript点击
                        click_result = target_frame.run_js("""
                                                                var btn = document.querySelector('div.table-body-col__btn-upload');
                                                                if (btn) {
                                                                    btn.click();
                                                                    return "点击成功";
                                                                } else {
                                                                    return "未找到按钮元素";
                                                                }
                                                            """)
                        
                        logger.info(f"JavaScript点击结果: {click_result}")
                        is_clicked = True
                        break
    except Exception as e:
        logger.info(f"操作失败：{str(e)}")
        logger.info(f"当前URL：{cur_page.url}")
        logger.info(f"iframe数量：{len(cur_page.eles('t:iframe'))}")

###########################################
# 导航函数 - 来自main.py
###########################################

def navigateToNextPage(cur_page):
    """翻页并获取数据"""
    global current_page_num
    # current_page_num和total_page_num是全局变量，分别表示当前页码和总页码
    
    # 保存页面源码以便调试
    try:
        debug_filename = f"page_source_{current_page_num}.html"
        with open(os.path.join(DATA_DIR, debug_filename), 'w', encoding='utf-8') as f:
            f.write(cur_page.html)
        logger.info(f"已保存当前页面源码到: {debug_filename}")
    except Exception as e:
        logger.info(f"保存页面源码失败: {str(e)}")
    
    try:
        start_time = time.time()
        is_end = False
        # 获取所有iframe元素（<iframe>标签）
        iframe_eles = cur_page.eles('t:iframe')  # 通过标签名获取所有iframe元素
        if iframe_eles:
            for iframe_ele in iframe_eles:
                # 从iframe元素中获取name属性
                frame_name = iframe_ele.attr("name")
                if frame_name == "aigc":
                    while True:
                        if is_end:
                            logger.info("已到达最后一页")
                            break
                        # 自动点击章节详情
                        target_frame = cur_page.get_frame(frame_name)
                        # 自动点击章节详情
                        click_result = target_frame.run_js("""
                            // 使用querySelectorAll选择所有带有"word"类的元素
                            var wordElements = document.querySelectorAll('.word');
                            var clicked = false;
                            wordCnt = wordElements.length;
                            if (wordCnt == 0) {
                                return "未找到word元素"
                            }
                            // 遍历所有元素，查找包含"第"字的元素并点击
                            for (var i = 0; i < wordElements.length; i++) {
                                var element = wordElements[i];
                                    // 修复：移除错误的右括号，并使用箭头函数包装点击操作
                                    element.click();
                                    clicked = true;
                            }
                            
                            if (clicked) {
                                return "点击成功" + wordCnt + "个元素";
                            } else {
                                return "未找到包含'第'字的元素" + wordElements.length;
                            }
                        """)
                        logger.info(f"章节点击：JavaScript点击结果: {click_result}")
                        # 使用JavaScript点击下一页按钮
                        click_result = target_frame.run_js("""
                           var btn = document.querySelector('button.btn-next');
                           if (btn) {
                               btn.click();
                               return "点击成功";
                           } else {
                               return "未找到按钮元素";
                           }
                       """)

                        current_page_num += 1
                        logger.info(f"翻页: {current_page_num} / {total_page_num}")
                        if 0 < total_page_num <= current_page_num:
                            is_end = True
                            break
                        time.sleep(2)

    except Exception as e:
        logger.info(f"操作失败：{str(e)}")
        logger.info(f"当前URL：{cur_page.url}")
        logger.info(f"iframe数量：{len(cur_page.eles('t:iframe'))}")

###########################################
# UI部分 - 来自ui.py
###########################################

class DataExportUI:
    """数据导出UI类"""
    def __init__(self, root):
        self.root = root
        self.root.title("腾讯音乐数据导出 - 实时监控")
        self.root.geometry("1000x800")  # 增加高度以容纳日志框
        
        # 创建顶部框架
        self.top_frame = tk.Frame(root)
        self.top_frame.pack(fill=tk.X, padx=10, pady=10)
        
        # 创建开始爬取数据按钮
        self.start_crawl_btn = tk.Button(self.top_frame, text="开始爬取数据", command=self.start_crawling)
        self.start_crawl_btn.pack(side=tk.LEFT, padx=5)
        
        # 创建导出按钮
        self.export_btn = tk.Button(self.top_frame, text="导出未完成章节", command=self.export_unfinished)
        self.export_btn.pack(side=tk.LEFT, padx=5)
        
        # 创建刷新按钮
        self.refresh_btn = tk.Button(self.top_frame, text="手动刷新数据", command=self.load_data)
        self.refresh_btn.pack(side=tk.LEFT, padx=5)
        
        # 创建清除日志按钮
        self.clear_log_btn = tk.Button(self.top_frame, text="清除日志", command=self.clear_log)
        self.clear_log_btn.pack(side=tk.LEFT, padx=5)
        
        # 创建状态标签
        self.status_label = tk.Label(self.top_frame, text="就绪")
        self.status_label.pack(side=tk.LEFT, padx=20)
        
        # 创建数据统计标签
        self.stats_frame = tk.Frame(root)
        self.stats_frame.pack(fill=tk.X, padx=10, pady=5)
        
        self.roles_label = tk.Label(self.stats_frame, text="角色数据: 未加载")
        self.roles_label.pack(side=tk.LEFT, padx=10)
        
        self.chapters_label = tk.Label(self.stats_frame, text="章节数据: 未加载")
        self.chapters_label.pack(side=tk.LEFT, padx=10)
        
        self.details_label = tk.Label(self.stats_frame, text="章节详情: 未加载")
        self.details_label.pack(side=tk.LEFT, padx=10)
        
        # 创建主框架，使用PanedWindow分割表格和日志
        self.main_paned = ttk.PanedWindow(root, orient=tk.VERTICAL)
        self.main_paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 创建表格框架
        self.table_frame = tk.Frame(self.main_paned)
        self.main_paned.add(self.table_frame, weight=3)  # 表格占3/4
        
        # 创建日志框架
        self.log_frame = tk.Frame(self.main_paned)
        self.main_paned.add(self.log_frame, weight=1)  # 日志占1/4
        
        # 创建日志标签
        log_label = tk.Label(self.log_frame, text="运行日志")
        log_label.pack(anchor=tk.W, padx=5, pady=2)
        
        # 创建日志文本框
        self.log_text = scrolledtext.ScrolledText(self.log_frame, height=10, wrap=tk.WORD)
        self.log_text.pack(fill=tk.BOTH, expand=True)
        self.log_text.config(state=tk.DISABLED)  # 设置为只读
        
        # 记录上次加载的数据量，用于检测更新
        self.last_loaded_chapters = 0
        self.last_loaded_details = 0
        
        # 创建表格
        self.create_table()
        
        # 启动日志处理
        self.process_logs()
    
    def create_table(self):
        """创建数据表格"""
        # 创建滚动条
        scrollbar_y = ttk.Scrollbar(self.table_frame)
        scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
        
        scrollbar_x = ttk.Scrollbar(self.table_frame, orient=tk.HORIZONTAL)
        scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 创建表格
        self.tree = ttk.Treeview(
            self.table_frame,
            columns=("章节", "状态", "配音员", "未完成数"),
            show="headings",
            yscrollcommand=scrollbar_y.set,
            xscrollcommand=scrollbar_x.set
        )
        
        # 配置列
        self.tree.column("章节", width=400)
        self.tree.column("状态", width=100)
        self.tree.column("配音员", width=200)
        self.tree.column("未完成数", width=100)
        
        # 配置表头
        self.tree.heading("章节", text="章节")
        self.tree.heading("状态", text="状态")
        self.tree.heading("配音员", text="配音员")
        self.tree.heading("未完成数", text="未完成数")
        
        # 放置表格
        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # 配置滚动条
        scrollbar_y.config(command=self.tree.yview)
        scrollbar_x.config(command=self.tree.xview)
        
        # 加载数据
        self.load_data()
    
    def load_data(self):
        """加载数据到表格"""
        try:
            # 更新数据统计
            self.update_data_stats()
            
            # 清空表格
            for item in self.tree.get_children():
                self.tree.delete(item)
            
            # 尝试加载章节列表数据
            chapter_list_path = os.path.join(DATA_DIR, "chapter_list.json")
            if os.path.exists(chapter_list_path):
                with open(chapter_list_path, 'r', encoding='utf-8') as f:
                    chapter_data = json.load(f)
                
                if 'data' in chapter_data and 'list' in chapter_data['data']:
                    chapters = chapter_data['data']['list']
                    
                    # 计算每个章节的未完成数量
                    chapter_error_counts = self.get_chapter_error_counts()
                    
                    for chapter in chapters:
                        chapter_id = str(chapter.get('id', ""))
                        title = chapter.get('title', "未知章节")
                        status = chapter.get('chapter_voice_status', "未知状态")
                        
                        # 获取未完成数量
                        error_count = chapter_error_counts.get(chapter_id, 0)
                        
                        # 插入数据
                        self.tree.insert("", tk.END, values=(title, status, "", error_count))
                    
                    self.status_label.config(text=f"已加载 {len(chapters)} 个章节")
                    self.last_loaded_chapters = len(chapters)
                else:
                    self.status_label.config(text="章节数据格式不正确")
            else:
                self.status_label.config(text="未找到章节数据文件")
        
        except Exception as e:
            self.status_label.config(text=f"加载数据出错: {str(e)}")
            import traceback
            traceback.print_exc()
    
    def get_chapter_error_counts(self):
        """获取每个章节的错误行数量"""
        chapter_error_counts = {}
        
        # 遍历所有章节详情文件
        for json_file in glob.glob(os.path.join(DATA_DIR, 'chapter_detail_*.json')):
            try:
                with open(json_file, 'r', encoding='utf-8') as f:
                    chapter_data = json.load(f)
                
                # 提取章节ID
                chapter_id = os.path.basename(json_file).split('_')[-1].replace('.json', '')
                
                # 获取错误行数量
                error_lines = chapter_data.get('data', {}).get('error_lines', [])
                if error_lines:
                    chapter_error_counts[chapter_id] = len(error_lines)
            except Exception as e:
                logger.info(f"处理章节详情文件出错 {json_file}: {str(e)}")
        
        return chapter_error_counts
    
    def update_data_stats(self):
        """更新数据统计信息"""
        # 检查角色数据
        roles_path = os.path.join(DATA_DIR, "roles.json")
        if os.path.exists(roles_path):
            try:
                with open(roles_path, 'r', encoding='utf-8') as f:
                    roles_data = json.load(f)
                roles_count = len(roles_data.get('data', {}).get('list', []))
                self.roles_label.config(text=f"角色数据: {roles_count} 个角色")
            except:
                self.roles_label.config(text="角色数据: 格式错误")
        else:
            self.roles_label.config(text="角色数据: 未加载")
        
        # 检查章节列表数据
        chapter_list_path = os.path.join(DATA_DIR, "chapter_list.json")
        if os.path.exists(chapter_list_path):
            try:
                with open(chapter_list_path, 'r', encoding='utf-8') as f:
                    chapter_data = json.load(f)
                chapters_count = len(chapter_data.get('data', {}).get('list', []))
                self.chapters_label.config(text=f"章节数据: {chapters_count} 个章节")
            except:
                self.chapters_label.config(text="章节数据: 格式错误")
        else:
            self.chapters_label.config(text="章节数据: 未加载")
        
        # 检查章节详情数据
        chapter_details = glob.glob(os.path.join(DATA_DIR, 'chapter_detail_*.json'))
        self.details_label.config(text=f"章节详情: {len(chapter_details)} 个详情")
        
        # 如果数据有更新，更新状态标签
        if self.last_loaded_chapters != len(chapter_details):
            self.last_loaded_details = len(chapter_details)
            self.status_label.config(text=f"数据已更新: {datetime.now().strftime('%H:%M:%S')}")
    
    def process_logs(self):
        """处理日志队列中的消息"""
        try:
            # 检查队列中是否有日志消息
            while not log_queue.empty():
                # 获取日志记录
                record = log_queue.get(block=False)
                
                # 格式化日志消息
                formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%H:%M:%S')
                log_message = formatter.format(record)
                
                # 将日志消息添加到日志框
                self.log_text.config(state=tk.NORMAL)  # 临时启用编辑
                self.log_text.insert(tk.END, log_message + '\n')
                
                # 自动滚动到最新的日志消息
                self.log_text.see(tk.END)
                
                # 根据日志级别设置颜色
                if record.levelno >= logging.ERROR:
                    self.log_text.tag_add("error", "end-{}c linestart".format(len(log_message) + 1), "end-1c")
                    self.log_text.tag_config("error", foreground="red")
                elif record.levelno >= logging.WARNING:
                    self.log_text.tag_add("warning", "end-{}c linestart".format(len(log_message) + 1), "end-1c")
                    self.log_text.tag_config("warning", foreground="orange")
                elif record.levelno >= logging.INFO:
                    self.log_text.tag_add("info", "end-{}c linestart".format(len(log_message) + 1), "end-1c")
                    self.log_text.tag_config("info", foreground="blue")
                
                self.log_text.config(state=tk.DISABLED)  # 恢复只读状态
        except Exception as e:
            # 如果处理日志时出错，记录错误但不中断程序
            logger.info(f"处理日志时出错: {str(e)}")
        
        # 每100毫秒检查一次日志队列
        self.root.after(100, self.process_logs)
    
    def clear_log(self):
        """清除日志框中的内容"""
        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.config(state=tk.DISABLED)
        logger.info("日志已清除")
        
    def start_crawling(self):
        """开始爬取数据"""
        global start_crawling_event
        
        # 确认是否开始爬取
        if messagebox.askyesno("确认", "确定要开始爬取数据吗？这将打开浏览器并开始数据采集过程。"):
            logger.info("用户已确认开始爬取数据")
            self.start_crawl_btn.config(state=tk.DISABLED)  # 禁用按钮防止重复点击
            start_crawling_event.set()  # 设置事件，触发数据爬取
            self.status_label.config(text="数据爬取已启动")
        else:
            logger.info("用户取消了数据爬取")
    
    def export_unfinished(self):
        """异步导出未完成章节"""
        def _export_worker():
            try:
                # 收集结果数据
                result_data = []
                
                # 读取角色数据
                roles_path = os.path.join(DATA_DIR, "roles.json")
                if os.path.exists(roles_path):
                    with open(roles_path, 'r', encoding='utf-8') as f:
                        roles_data = json.load(f)
                    
                    # 构建cv_id到cv_name的映射
                    cv_id_name_map = {str(role['cv_id']): role['cv_name']
                                    for role in roles_data['data']['list'] if 'cv_id' in role}
                    cv_id_role_map = {str(role['cv_id']): role['name']
                                    for role in roles_data['data']['list'] if 'cv_id' in role}
                else:
                    self.root.after(0, lambda: messagebox.showerror("错误", "未找到角色数据文件"))
                    return
                
                # 读取章节列表数据
                chapter_list_path = os.path.join(DATA_DIR, "chapter_list.json")
                if os.path.exists(chapter_list_path):
                    with open(chapter_list_path, 'r', encoding='utf-8') as f:
                        chapter_list_data = json.load(f)
                    
                    # 构建章节ID到标题的映射
                    chapter_id_title = {str(chapter['id']): chapter['title']
                                        for chapter in chapter_list_data['data']['list']}
                else:
                    self.root.after(0, lambda: messagebox.showerror("错误", "未找到章节列表数据文件"))
                    return
                
                # 遍历所有章节详情文件
                for json_file in glob.glob(os.path.join(DATA_DIR, 'chapter_detail_*.json')):
                    with open(json_file, 'r', encoding='utf-8') as f:
                        chapter_data = json.load(f)
                    
                    # 检查是否有未完成的录音
                    data = chapter_data.get('data', {})
                    
                    error_lines = data.get('error_lines', [])
                    if not error_lines:
                        continue  # 没有错误行则跳过
                    
                    # 提取章节ID
                    chapter_id = os.path.basename(json_file).split('_')[-1].replace('.json', '')
                    chapter_title = chapter_id_title.get(chapter_id, f"未知章节({chapter_id})")
                    
                    # 获取章节内容文本
                    content_url = data.get('chapter_content', {}).get('url', '')
                    if not content_url:
                        continue
                    
                    try:
                        # 下载文本内容
                        response = requests.get(content_url, timeout=10)
                        response.encoding = 'utf-8'
                        content_lines = response.text.splitlines()
                    except Exception as e:
                        logger.info(f"获取章节内容失败 {chapter_id}: {str(e)}")
                        continue
                    
                    # 处理错误行
                    for line_num in error_lines:
                        # 处理错误行
                        target_line = None
                        # 查找对应行（支持行号后缀匹配，如 00009 匹配 00238-00009）
                        for line in content_lines:
                            if str.__contains__(line, line_num):
                                target_line = line
                                break
                        if not target_line:
                            try:
                                target_line = content_lines[int(line_num) - 1]
                            except IndexError:
                                continue
                        
                        if not target_line:
                            continue
                        
                        # 提取userid
                        userid_match = re.search(r'userid="(\d+)"', target_line)
                        if not userid_match:
                            continue
                        
                        userid = userid_match.group(1)
                        cv_name = cv_id_name_map.get(userid, f"未知CV({userid})")
                        role_name = cv_id_role_map.get(userid, f"未知角色({userid})")
                        
                        # 提取标签内的文字
                        label_text_match = re.search(r'<[^>]*>([^<]+)</[^>]*>', target_line)
                        if not label_text_match:
                            continue
                        
                        label_text = label_text_match.group(1)
                        
                        # 添加到结果
                        result_data.append({
                            'cv昵称': cv_name,
                            '角色名称': role_name,
                            '章节ID': chapter_id,
                            '章节标题': chapter_title,
                            '待录台词': label_text
                        })
                
                # 导出到Excel
                if not result_data:
                    self.root.after(0, lambda: messagebox.showinfo("提示", "未找到需要处理的未完成章节"))
                    return
                
                df = pd.DataFrame(result_data)
                output_path = "未完成章节录音分配.xlsx"
                df.to_excel(output_path, index=False)
                
                # 在主线程中显示完成消息
                self.root.after(0, lambda: messagebox.showinfo(
                    "成功", 
                    f"已导出{len(result_data)}条记录到{output_path}"
                ))
                
            except Exception as e:
                self.root.after(0, lambda: messagebox.showerror(
                    "错误", 
                    f"导出失败: {str(e)}"
                ))
                logger.error(f"导出失败: {str(e)}")
                import traceback
                logger.error(traceback.format_exc())
        
        # 禁用导出按钮防止重复点击
        self.export_btn.config(state=tk.DISABLED)
        self.status_label.config(text="正在导出数据...")
        
        # 启动后台线程执行导出
        export_thread = threading.Thread(target=_export_worker, daemon=True)
        export_thread.start()
        
        # 添加线程完成后的回调
        def _on_export_complete():
            self.export_btn.config(state=tk.NORMAL)
            self.status_label.config(text="导出完成")
        
        # 定期检查线程状态
        def _check_thread():
            if export_thread.is_alive():
                self.root.after(500, _check_thread)
            else:
                self.root.after(0, _on_export_complete)
        
        self.root.after(500, _check_thread)

###########################################
# UI启动函数
###########################################

def start_ui():
    """在单独的线程中启动UI界面"""
    logger.info("正在启动UI界面...")
    root = tk.Tk()
    root.title("腾讯音乐数据导出 - 实时监控")
    app = DataExportUI(root)
    
    # 创建一个函数来定期刷新UI数据
    def refresh_data():
        try:
            app.load_data()  # 重新加载数据
            # 每5秒刷新一次数据
            root.after(5000, refresh_data)
        except Exception as e:
            logger.error(f"刷新UI数据时出错: {str(e)}")
            import traceback
            logger.debug(traceback.format_exc())
    
    # 启动定时刷新
    root.after(1000, refresh_data)
    
    # 启动UI主循环
    root.mainloop()

###########################################
# 主函数和程序入口点
###########################################

async def main():
    """主函数"""
    global total_page_num
    total_page_num = math.ceil(2607 / 20)+1
    
    # 等待用户点击"开始爬取数据"按钮
    logger.info("等待用户点击'开始爬取数据'按钮...")
    while not start_crawling_event.is_set():
        await asyncio.sleep(0.5)  # 每0.5秒检查一次事件状态
    
    logger.info("开始爬取数据流程")
    
    while True:
        if current_page_num <= total_page_num:
            browser = None
            try:
                logger.info("=== 启动程序 ===")
                logger.info(f"数据将保存到目录: {DATA_DIR}")

                logger.info("初始化浏览器...")
                browser = ChromiumPage()
                tab = browser  # ChromiumPage对象本身就是一个标签页

                # 定义需要监听的URL
                graphql_urls = [
                    'https://gateway.kugou.com/longebook/v1/ai_design_album/roles',
                    'https://gateway.kugou.com/longebook/v2/ai_design_anchor_task_center/chapter_list',
                    'https://gateway.kugou.com/longebook/v1/ai_design_anchor_task_center/chapter_detail',
                    # 可以根据需要添加更多URL
                ]

                logger.info("设置网络监听...")
                await start_listening_async(tab, graphql_urls)

                logger.info(f"已开始监听 {len(graphql_urls)} 个URL:")
                for i, url in enumerate(graphql_urls, 1):
                    logger.info(f"  {i}. {url}")

                # 创建异步任务来处理数据包
                logger.info("创建数据包处理任务...")
                packet_task = asyncio.create_task(process_packets_async(tab))

                # 导航到目标网站
                logger.info("导航到腾讯音乐网站...")
                tab.get('https://mp.tencentmusic.com/index')

                # 继续执行其他代码
                logger.info("等待用户登录...")
                login(tab)

                logger.info("获取章节标题...")
                getChapterTitle(tab)

                # 执行自动翻页
                logger.info("自动翻页中...")
                navigateToNextPage(tab)

                logger.info("程序主流程执行完毕，继续监听网络请求...")
                logger.info("提示: 按Ctrl+C可以终止程序")

                # 等待数据包处理任务完成（实际上这个任务会一直运行，直到程序被终止）
                try:
                    await packet_task
                except asyncio.CancelledError:
                    logger.warning("数据包处理任务被取消")

            except KeyboardInterrupt:
                logger.info("\n用户终止程序")
            except Exception as e:
                logger.error(f"程序执行出错: {str(e)}")
                import traceback
                logger.debug(traceback.format_exc())
            finally:
                # 关闭浏览器
                if browser:
                    try:
                        browser.quit()
                        logger.info("浏览器已关闭")
                    except:
                        pass
                logger.info("=== 程序结束 ===")
        else:
            break
    
    # 等待UI线程结束（实际上不会执行到这里，因为UI线程是daemon线程）
    logger.info("数据获取完成，UI界面仍在运行")
    
    # 如果需要，可以在这里添加一个无限循环，让主线程保持运行
    # 这样即使数据获取完成，程序也不会退出，UI界面可以继续运行
    while ui_thread.is_alive():
        await asyncio.sleep(1)

# 定义退出处理函数
def handle_exit():
    """程序退出时的清理工作"""
    logger.info("正在执行退出清理...")
    try:
        # 关闭可能的浏览器实例
        # 这里不需要实际操作，因为浏览器会在main函数中关闭
        pass
    except:
        pass

# 修改入口点以支持异步主函数
if __name__ == "__main__":
    try:
        # 注册退出处理函数
        import atexit
        atexit.register(handle_exit)

        # 在程序启动时立即启动UI界面
        ui_thread = threading.Thread(target=start_ui, daemon=False)
        ui_thread.start()
        logger.info("UI界面已启动")

        # 给UI线程一点时间启动
        time.sleep(1)

        # 在主线程中运行异步主程序
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        try:
            loop.run_until_complete(main())
        finally:
            loop.close()
    except KeyboardInterrupt:
        logger.info("\n程序被用户中断")
    except Exception as e:
        logger.info(f"程序运行出错: {str(e)}")
        import traceback
        traceback.print_exc()